﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;

namespace Elca.MvvmHelpers {
    public class CanvasTransformer {

        private static readonly Dictionary<INotifyCollectionChanged, Canvas> s_notifyListMap = new Dictionary<INotifyCollectionChanged, Canvas>();
        private static readonly Dictionary<Canvas, IEnumerable> s_canvasMap = new Dictionary<Canvas, IEnumerable>();

        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.RegisterAttached(
                "ItemsSource", 
                typeof (IEnumerable), 
                typeof (CanvasTransformer), 
                new PropertyMetadata(null, OnItemsSourceChanged));

        public static readonly DependencyProperty IsItemsSourceEnabledProperty = DependencyProperty.RegisterAttached(
                "IsItemsSourceEnabled",
                typeof(bool),
                typeof(CanvasTransformer),
                new PropertyMetadata(false, OnIsItemsSourceEnabledChanged));

        public static IEnumerable GetItemsSource(Canvas cv) {
            return (IEnumerable) cv.GetValue(ItemsSourceProperty); 
        }

        public static void SetItemsSource(Canvas cv, IEnumerable list) {
            cv.SetValue(ItemsSourceProperty, list);
        }

        public static bool GetIsItemsSourceEnabled(Canvas cv) {
            return (bool) cv.GetValue(IsItemsSourceEnabledProperty);
        }

        public static void SetIsItemsSourceEnabled(Canvas cv, bool b) {
            cv.SetValue(IsItemsSourceEnabledProperty, b);
        }

        private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {

            Canvas cv = d as Canvas;
            if (cv == null) {
                throw new InvalidOperationException("The ItemsSource dependency property is only for a Canvas");
            }

            IEnumerable oldData = e.OldValue as IEnumerable;
            if (oldData != null) {
                ForgetAboutCollection(oldData);
            }

            IEnumerable data = e.NewValue as IEnumerable;

            if (data != null) {

// ReSharper disable PossibleMultipleEnumeration
                s_canvasMap[cv] = data;

                if (GetIsItemsSourceEnabled(cv)) {
                    RebuildChildren(cv, data);
                }
// ReSharper restore PossibleMultipleEnumeration

            }
        }

        public static void ForgetAboutCollection(IEnumerable coll) {

            INotifyCollectionChanged ncc = coll as INotifyCollectionChanged;
            if (ncc != null) {

                if (s_notifyListMap.ContainsKey(ncc)) {

                    Canvas cv = s_notifyListMap[ncc];
                    s_canvasMap.Remove(cv);
                    s_notifyListMap.Remove(ncc);
                }
                ncc.CollectionChanged -= OnSourceCollectionChanged;
            }
        }

        private static void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {

            INotifyCollectionChanged data = (INotifyCollectionChanged) sender;
            Canvas cv = s_notifyListMap[data];
            switch (e.Action) {
            
                case NotifyCollectionChangedAction.Reset:
                    RebuildChildren(cv, (IEnumerable) data);
                    break;

                case NotifyCollectionChangedAction.Add:
                    AddChildren(cv, e.NewItems);
                    break;
            }
        }

        private static void OnIsItemsSourceEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            
            Canvas cv = d as Canvas;
            if (cv == null) {
                throw new InvalidOperationException("The ItemsSource dependency property is only for a Canvas");
            }

            bool isEnabled = (bool) e.NewValue;

            if (isEnabled) {

                IEnumerable data = s_canvasMap[cv];
                RebuildChildren(cv, data);
            }
            // changing the value to false has no effect
        }

        public static void ForceRebuildChildren(INotifyCollectionChanged collection) {
            Canvas cv = s_notifyListMap[collection];
            RebuildChildren(cv, (IEnumerable)collection);
        }

        private static void RebuildChildren(Canvas cv, IEnumerable data) {

            INotifyCollectionChanged ncc = data as INotifyCollectionChanged;
            if (ncc != null) {

                // subscribe just once
                if (!s_notifyListMap.ContainsKey(ncc)) {
                    s_notifyListMap[ncc] = cv;
                    ncc.CollectionChanged += OnSourceCollectionChanged;
                }
            }

            cv.Children.Clear();
            AddChildren(cv, data);
        }

        private static void AddChildren(Canvas cv, IEnumerable data) {
            foreach (object o in data) {
                CreateChild(cv, o);
            }
            
            FrameworkElement fe = cv.Parent as FrameworkElement;
            if (fe != null) {
                fe.Width = cv.Children.Count * 20.0;
            } 
        }

        private static void CreateChild(Canvas cv, object o) {

            int count = cv.Children.Count;
            ContentControl child = new ContentControl {
                    Content = o
            };
            Canvas.SetLeft(child, count * 20.0);
            cv.Children.Add(child);
        }
    }
}
